
namespace Patterns4Net.PatternEnforcer.Core.Patterns
{
    using System.Linq;
    using Mono.Cecil;
    using Mono.Cecil.Rocks;
    using Attributes.DesignPatterns;
    using Patterns4Net.Core.CecilExtensions;
    using Patterns4Net.Core.Patterns;

    [PatternCheckerExport(typeof(Composite))]
    public class CompositePatternChecker : FluentPatternChecker<Composite>
    {
        public CompositePatternChecker()
        {
            this.Check(
                pattern => pattern.CompositeType.Implements(pattern.Component),
                CompositeDoesNotImplementComponentError);

            this.Check(
                pattern => pattern.ComponentsField != null,
                CompositeDoesNotContainComponentsCollectionError);

            this.Type(pattern => pattern.CompositeType)
                .Methods(
                    (pattern, method) =>
                        method.HasAttribute<NotCompositeAttribute>() == false &&
                        pattern.Component.GetMethods().Any(componentMethod => method.Overrides(componentMethod)))
                .Check(
                    (pattern, method) =>
                        IteratesThruCollection(pattern.ComponentsField, method) ||
                        UsesLinqOnCollection(pattern.ComponentsField, method),
                    (pattern, method) =>
                        string.Format(
                            ErrorMessages.CompositeMethodDoesNotIterateThruComponents,
                            method.Name,
                            pattern.CompositeType.Name)
                );
        }

        private static bool IteratesThruCollection(FieldReference field, MethodDefinition method)
        {
            return method.GetMethodCalls().Any(call =>
                call.HasTargetObject &&
                IsGetEnumeratorCall(call.Method) &&
                call.TargetObject.IsField &&
                call.TargetObject.FieldOwner != null &&
                call.TargetObject.FieldOwner.IsThisParameter &&
                call.TargetObject.FieldReference == field);
        }

        private static bool IsGetEnumeratorCall(MethodReference m)
        {
            return m.DeclaringType.FullName.StartsWith("System.Collections.Generic.IEnumerable") &&
                   m.Name == "GetEnumerator";
        }

        private static bool UsesLinqOnCollection(FieldReference field, MethodDefinition method)
        {
            return method.GetMethodCalls().Any(call =>
                call.Method.DeclaringType.IsEqual(typeof(Enumerable)) &&
                call.Parameters.Count() > 0 &&
                call.Parameters[0].IsField &&
                call.Parameters[0].FieldOwner != null &&
                call.Parameters[0].FieldOwner.IsThisParameter &&
                call.Parameters[0].FieldReference == field);
        }

        private static string CompositeDoesNotImplementComponentError(Composite pattern)
        {
            return string.Format(
                ErrorMessages.CompositeDoesNotImplementComponentInterface, 
                pattern.CompositeType.Name, 
                pattern.Component.Name);
        }

        private static string CompositeDoesNotContainComponentsCollectionError(Composite pattern)
        {
            return string.Format(
                ErrorMessages.CompositeDoesNotContainComponentsCollection,
                pattern.CompositeType.Name,
                pattern.Component.Name);
        }
    }
}
